#pragma once
#include <memory>
#include <functional>
#include <unordered_map>
#include <string>
#include <stdexcept>

namespace ngintoy
{
    namespace utils
    {
        template <class Base, class ...CtorArgs>
        struct factory
        {
            using self_type = factory;
            using name_type = std::string;

            using base_type = Base;
            using base_ptr  = std::unique_ptr<base_type>;

            using maker_fn  = base_ptr (CtorArgs...);
            using maker     = std::function<maker_fn>;

            static auto& registry()
            {
                static std::unordered_map<name_type, maker> reg;
                return reg;
            }

            virtual ~factory() {}

            template <class Concrete>
            struct registrar
            {
                using concrete_type = Concrete;

                struct invalid_registration : std::logic_error
                {
                    using logic_error::logic_error;
                };

                registrar(name_type name)
                {
                    auto& r = registry();
                    if (r.find(name) != r.end())
                        throw invalid_registration{"name already exists: " + name};

                    r[name] = [] (CtorArgs... args) -> base_ptr {
                        return std::make_unique<concrete_type>(args...);
                    };
                }
            };

        protected:
            factory() {}
        };
    }
}

